﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Data;

namespace GcTicket.BLL.Data
{
    public static class EntityUtility
    {
        /// <summary>
        /// 将一个对象的值传送到另一个对象。
        /// </summary>
        /// <param name="source">源对象。</param>
        /// <param name="des">目标对象。</param>
        /// <param name="props">选择要/不传送的属性/字段名数组。</param>
        /// <param name="selectOption">选择类型。即props里的属性/字段是包括还是排除。</param>
        /// <param name="option">传送选项。</param>
        public static void Transfer(object source, object des, string[] props, SelectOption selectOption, EntityTransferOption option)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (des == null) throw new ArgumentNullException("des");

            IList filterProps = null;
            if (props != null) filterProps = new ArrayList(props);

            Type sourceType = source.GetType();
            Type desType = des.GetType();
            PropertyInfo[] srcProps = sourceType.GetProperties();
            bool explicitInclude;
            foreach (PropertyInfo prop in srcProps)
            {
                if (!prop.CanRead) continue;
                // 只读属性除非明确要求，否则不传递。
                if (!prop.CanWrite)
                {
                    if (!((option & EntityTransferOption.SourceReadonly) == EntityTransferOption.SourceReadonly))
                    {
                        continue;
                    }
                }

                PropertyInfo desProp = desType.GetProperty(prop.Name);
                if (desProp == null) continue;
                if (!IsInclude(filterProps, selectOption, prop.Name, out explicitInclude)) continue;

                object sourceValue = prop.GetValue(source, null);

                if (!desProp.CanWrite)
                {
                    // if explicit include, replace with set value by field 
                    string fieldName = prop.Name;
                    FieldInfo desField = desType.GetField(fieldName,
                        BindingFlags.NonPublic |
                        BindingFlags.Instance |
                        BindingFlags.GetField |
                        BindingFlags.Public |
                        BindingFlags.IgnoreCase
                        );
                    if (desField == null) continue;

                    CopyValue(sourceValue, des, desField);
                }
                else
                {
                    CopyValue(sourceValue, des, desProp);
                }
            }

            FieldInfo[] sourcecFields = sourceType.GetFields();
            foreach (FieldInfo field in sourcecFields)
            {
                FieldInfo desField = desType.GetField(field.Name);
                if (desField == null) continue;

                if (!IsInclude(filterProps, selectOption, field.Name, out explicitInclude)) continue;

                object sourceValue = field.GetValue(source);
                CopyValue(sourceValue, des, desField);
            }
        }

        private static void CopyValue(object sourceValue, object des, FieldInfo desField)
        {
            if (IsBaseType(desField.FieldType)) desField.SetValue(des, sourceValue);
            else
            {
                object desValue = desField.GetValue(des);
                if (desValue != null)
                {
                    if (sourceValue == null) desField.SetValue(des, sourceValue);
                    else
                    {
                        Transfer(sourceValue, desValue);
                    }
                }
            }
        }

        private static bool IsBaseType(Type type)
        {
            return (type.IsValueType || type.IsPrimitive || type == typeof(string));
        }

        private static void CopyValue(object sourceValue, object des, PropertyInfo desProp)
        {
            if (IsBaseType(desProp.PropertyType)) desProp.SetValue(des, sourceValue, null);
            else
            {
                object desValue = desProp.GetValue(des, null);
                if (desValue != null)
                {
                    if (sourceValue == null) desProp.SetValue(des, sourceValue, null);
                    else
                    {
                        Transfer(sourceValue, desValue);
                    }
                }
            }
        }

        private static bool IsInclude(IList props, SelectOption mode, string propName, out bool explicitInclude)
        {
            explicitInclude = false;

            if (props == null) return true;
            if (mode == SelectOption.Include)
            {
                explicitInclude = true;
                return props.Contains(propName);
            }
            else
            {
                return !props.Contains(propName);
            }
        }

        /// <summary>
        /// 从源对象数组传递值,并生成目的数组
        /// </summary>
        /// <typeparam name="DescEntity">目的数据类型</typeparam>
        /// <typeparam name="SourceEntity">源对象数据类型</typeparam>
        /// <param name="sources">源对象数组</param>
        /// <returns>生成的目的对象数组</returns>
        public static List<DescEntity> Transfer<SourceEntity, DescEntity>(List<SourceEntity> sources) where DescEntity : new()
        {
            List<DescEntity> result = new List<DescEntity>(sources.Count);
            for (int i = 0; i < sources.Count; i++)
            {
                result[i] = new DescEntity();
                Transfer(sources[i], result[i]);
            }

            return result;
        }

        /// <summary>
        /// 将一组对象的值传送到另一组对象。包括只读属性的值。
        /// </summary>
        /// <param name="source">源对象数组。</param>
        /// <param name="des">目标对象数组。</param>
        public static void Transfer(object[] source, object[] des)
        {
            if (source.Length != des.Length)
            {
                throw new ArgumentOutOfRangeException("The source length must be equals des.");
            }

            for (int i = 0; i < source.Length; i++)
            {
                Transfer(source[i], des[i], null, SelectOption.Exclude, EntityTransferOption.SourceReadonly);
            }
        }

        /// <summary>
        /// 将一个对象的值传送到另一个对象。包括只读属性的值。
        /// </summary>
        /// <param name="source">源对象。</param>
        /// <param name="des">目标对象。</param>
        public static void Transfer(object source, object des)
        {
            Transfer(source, des, null, SelectOption.Exclude, EntityTransferOption.SourceReadonly);
        }

        /// <summary>
        /// 将一个对象的值传送到另一个对象。包括只读属性的值。
        /// </summary>
        /// <param name="source">源对象。</param>
        /// <param name="des">目标对象。</param>
        /// <param name="option">传送选项。</param>
        public static void Transfer(object source, object des, EntityTransferOption option)
        {
            Transfer(source, des, null, SelectOption.Exclude, option);
        }

        /// <summary>
        /// 比较两个对象的属性及字段值
        /// </summary>
        /// <param name="source">源对象。</param>
        /// <param name="des">目标对象。</param>
        /// <returns>相等返回True</returns>
        public static bool EqualsObject(object source, object des)
        {
            return EqualsObject(source, des, null, SelectOption.Exclude);
        }

        /// <summary>
        /// <summary>
        /// 比较两个对象的属性及字段值
        /// </summary>
        /// <param name="source">源对象。</param>
        /// <param name="des">目标对象。</param>
        /// <param name="props">选择要/不比较的属性/字段名数组。</param>
        /// <param name="selectOption">选择类型。即props里的属性/字段是包括还是排除。</param>
        /// <returns>相等返回True</returns>
        public static bool EqualsObject(object source, object des, string[] props, SelectOption selectOption)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (des == null) throw new ArgumentNullException("des");

            IList filterProps = null;
            if (props != null) filterProps = new ArrayList(props);

            Type sourceType = source.GetType();
            Type desType = des.GetType();
            PropertyInfo[] srcProps = sourceType.GetProperties();
            bool explicitInclude;
            foreach (PropertyInfo prop in srcProps)
            {
                PropertyInfo desProp = desType.GetProperty(prop.Name);
                if (desProp == null) continue;
                if (!IsInclude(filterProps, selectOption, prop.Name, out explicitInclude)) continue;

                object sourceValue = prop.GetValue(source, null);

                if (!EqualsValue(sourceValue, des, desProp)) return false;
            }

            FieldInfo[] sourcecFields = sourceType.GetFields();
            foreach (FieldInfo field in sourcecFields)
            {
                FieldInfo desField = desType.GetField(field.Name);
                if (desField == null) continue;

                if (!IsInclude(filterProps, selectOption, field.Name, out explicitInclude)) continue;

                object sourceValue = field.GetValue(source);
                if (!EqualsValue(sourceValue, des, desField)) return false;
            }

            return true;
        }

        private static bool EqualsValue(object sourceValue, object des, FieldInfo desField)
        {
            object desValue = desField.GetValue(des);
            if (sourceValue == null && desValue == null) return true;

            if (IsBaseType(desField.FieldType))
            {
                return sourceValue.Equals(desValue);
            }
            else
            {
                if (desValue != null)
                {
                    if (sourceValue == null) return false;
                    else
                    {
                        return EqualsObject(sourceValue, desValue);
                    }
                }
            }
            return true;
        }

        private static bool EqualsValue(object sourceValue, object des, PropertyInfo desProp)
        {
            object desValue = desProp.GetValue(des, null);
            if (sourceValue == null && desValue == null) return true;

            if (IsBaseType(desProp.PropertyType))
            {
                return sourceValue.Equals(desValue);
            }
            else
            {
                if (desValue != null)
                {
                    if (sourceValue == null) return false;
                    else
                    {
                        return EqualsObject(sourceValue, desValue);
                    }
                }
            }

            return true;
        }
        static public DataTable ToDataTable<TResult>(this IEnumerable<TResult> value) where TResult : class
        {
            //创建属性的集合
            List<PropertyInfo> pList = new List<PropertyInfo>();
            //获得反射的入口
            Type type = typeof(TResult);
            DataTable dt = new DataTable();
            //把所有的public属性加入到集合 并添加DataTable的列
            Array.ForEach<PropertyInfo>(type.GetProperties(), p => { pList.Add(p); dt.Columns.Add(p.Name, p.PropertyType); });
            foreach (var item in value)
            {
                //创建一个DataRow实例
                DataRow row = dt.NewRow();
                //给row 赋值
                pList.ForEach(p => row[p.Name] = p.GetValue(item, null));
                //加入到DataTable
                dt.Rows.Add(row);
            }
            return dt;
        }
    }
    public enum SelectOption
    {
        Include,

        Exclude,
    }
    public enum EntityTransferOption
    {
        /// <summary>
        /// 空选项
        /// </summary>
        Empty = 00001,

        /// <summary>
        /// 要求传送源对象的只读属性
        /// </summary>
        SourceReadonly = 00010,
    }
}
